﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;

namespace iNet.Media.Xmp
{
	/// <summary>
	/// XMP Property 列舉器。
	/// </summary>
	public class XmpPropertyEnumerator : IEnumerator<XmpProperty>
	{
		#region 常數
		const string RDF_NAMESPACE_URI = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
		#endregion
		#region 欄位
		int _CurrentAttributeIndex = -1;
		XmpProperty _CurrentProperty;
		XmlNode _CurrentPropertyNode;
		XmlNode _CurrentRdfDescriptionNode;
		bool _Disposed;
		object _InitObject;
		long? _InitStreamPosition;
		XmlDocument _XmlDocument;
		#endregion
		#region 建構子
		/// <summary>
		/// 初始化新的 XmpPropertyEnumerator 執行個體。
		/// </summary>
		/// <param name="stream">提供 XMP 資料的資料流。</param>
		public XmpPropertyEnumerator(Stream stream)
		{
			//確認
			if (stream == null)
				throw new ArgumentNullException("stream");

			//初始化
			_InitObject = stream;
		}
		/// <summary>
		/// 初始化新的 XmpPropertyEnumerator 執行個體。
		/// </summary>
		/// <param name="document">帶有 XMP 資料的 XML 文件。</param>
		public XmpPropertyEnumerator(XmlDocument document)
		{
			//確認
			if (document == null)
				throw new ArgumentNullException("document");

			//初始化
			_InitObject = document;
		}
		#endregion
		#region Current
		/// <summary>
		/// 取得目前指向的 XMP Property。
		/// </summary>
		public XmpProperty Current
		{
			get
			{
				//確認狀態
				if (_Disposed)
					throw new ObjectDisposedException("XmpPropertyEnumerator");
				if (_CurrentProperty == null)
					throw new InvalidOperationException();

				//回傳
				return _CurrentProperty;
			}
		}
		#endregion
		#region Dispose
		/// <summary>
		/// 釋放列舉器所使用的資源。
		/// </summary>
		public void Dispose()
		{
			if (!_Disposed)
			{
				_Disposed = true;
				_InitObject = null;
				_XmlDocument = null;
				_CurrentProperty = null;
				_CurrentPropertyNode = null;
				GC.SuppressFinalize(this);
			}
		}
		#endregion
		#region IEnumerator.Current
		object IEnumerator.Current
		{
			get
			{
				return this.Current;
			}
		}
		#endregion
		#region IsRdfDescriptionNode
		bool IsRdfDescriptionNode(XmlNode node)
		{
			return (node.LocalName == "Description" && node.NamespaceURI == RDF_NAMESPACE_URI);
		}
		#endregion
		#region MoveNext
		/// <summary>
		/// 將列舉位置移動至下一個 XMP Property。
		/// </summary>
		/// <returns>True 表示成功移動列舉位置，False 表示已列舉至最後一個 XMP Property 或資料格式錯誤。</returns>
		/// <exception cref="ObjectDisposedException">列舉器已經處置。</exception>
		public bool MoveNext()
		{
			//確認狀態
			if (_Disposed)
				throw new ObjectDisposedException("XmpPropertyEnumerator");

			//載入 XML
			if (_XmlDocument == null)
			{
				_XmlDocument = new XmlDocument();
				try
				{
					if (_InitObject is Stream)
					{
						try
						{
							_InitStreamPosition = (_InitObject as Stream).Position;
						}
						catch
						{ }
						_XmlDocument.Load(_InitObject as Stream);
					}
					else if (_InitObject is string)
						_XmlDocument.LoadXml(_InitObject as string);
					else if (_InitObject is XmlDocument)
						_XmlDocument = (_InitObject as XmlDocument);
				}
				catch
				{
					return false;
				}
			}

			//讀取下一個 XMP 屬性
			while (true)
			{
				//移動至下一個節點
				if (_CurrentPropertyNode != null)
				{
					if (_CurrentPropertyNode is XmlAttribute)
					{
						++_CurrentAttributeIndex;
						if (_CurrentAttributeIndex < _CurrentRdfDescriptionNode.Attributes.Count)
							_CurrentPropertyNode = _CurrentRdfDescriptionNode.Attributes[_CurrentAttributeIndex];
						else
						{
							_CurrentAttributeIndex = -1;
							if (_CurrentRdfDescriptionNode.ChildNodes.Count > 0)
								_CurrentPropertyNode = _CurrentRdfDescriptionNode.FirstChild;
							else
							{
								XmlNode node = _CurrentRdfDescriptionNode.NextSibling;
								_CurrentPropertyNode = null;
								while (node != null)
								{
									if (this.IsRdfDescriptionNode(node))
									{
										_CurrentRdfDescriptionNode = node;
										if (node.Attributes.Count > 0)
										{
											_CurrentAttributeIndex = 0;
											_CurrentPropertyNode = node.Attributes[0];
											break;
										}
										else if (node.ChildNodes.Count > 0)
										{
											_CurrentPropertyNode = node.FirstChild;
											break;
										}
									}
									node = node.NextSibling;
								}
							}
						}
					}
					else
					{
						XmlNode node = _CurrentPropertyNode.NextSibling;
						if (node != null)
							_CurrentPropertyNode = node;
						else
						{
							node = _CurrentPropertyNode.ParentNode.NextSibling;
							_CurrentPropertyNode = null;
							while (node != null)
							{
								if (this.IsRdfDescriptionNode(node))
								{
									_CurrentRdfDescriptionNode = node;
									if (node.Attributes.Count > 0)
									{
										_CurrentAttributeIndex = 0;
										_CurrentPropertyNode = node.Attributes[0];
										break;
									}
									else if (node.ChildNodes.Count > 0)
									{
										_CurrentPropertyNode = node.FirstChild;
										break;
									}
								}
								node = node.NextSibling;
							}
						}
					}
				}
				else
				{
					XmlNode node = _XmlDocument.FirstChild;
					while (node != null)
					{
						if (node.LocalName == "xmpmeta" && node.NamespaceURI == "adobe:ns:meta/")
						{
							node = node.FirstChild;
							while (node != null)
							{
								if (node.LocalName == "RDF" && node.NamespaceURI == RDF_NAMESPACE_URI)
								{
									node = node.FirstChild;
									while (node != null)
									{
										if (this.IsRdfDescriptionNode(node))
										{
											_CurrentRdfDescriptionNode = node;
											if (node.Attributes.Count > 0)
											{
												_CurrentAttributeIndex = 0;
												_CurrentPropertyNode = node.Attributes[0];
												break;
											}
											else if (node.ChildNodes.Count > 0)
											{
												_CurrentPropertyNode = node.FirstChild;
												break;
											}
										}
										node = node.NextSibling;
									}
									break;
								}
								node = node.NextSibling;
							}
							break;
						}
						node = node.NextSibling;
					}
				}
				if (_CurrentPropertyNode == null)
					return false;
				else if (_CurrentPropertyNode is XmlAttribute)
				{
					if (_CurrentPropertyNode.NamespaceURI == RDF_NAMESPACE_URI || _CurrentPropertyNode.Prefix == "xmlns")
						continue;
				}

				//建立 XMP 屬性
				XmpProperty property = this.ReadXmpProperty(_CurrentPropertyNode);
				if (property == null)
					continue;
				_CurrentProperty = property;

				//讀取完成
				return true;
			}
		}
		#endregion
		#region ReadAllProperties
		/// <summary>
		/// 讀取所有 XMP Property。
		/// </summary>
		/// <returns>所有已讀取的 XMP Property。</returns>
		public List<XmpProperty> ReadAllProperties()
		{
			List<XmpProperty> list = new List<XmpProperty>();
			try
			{
				while (this.MoveNext())
					list.Add(this.Current);
			}
			finally
			{
				try
				{
					this.Reset();
				}
				catch
				{ }
			}
			return list;
		}
		#endregion
		#region ReadXmpProperty
		XmpProperty ReadXmpProperty(XmlNode propertyNode)
		{
			//取得 XMP Schema
			XmpSchema schema = CommonXmpSchemas.GetSchemaByNamespaceURI(propertyNode.NamespaceURI);
			if (schema == null || schema.NamespacePrefix != propertyNode.Prefix)
				schema = new XmpSchema(propertyNode.Prefix, propertyNode.NamespaceURI);

			//建立值
			if (propertyNode.ChildNodes.Count != 1)
				return null;
			XmpPropertyValue value = XmpPropertyValue.Create(propertyNode.FirstChild);

			//建立 XMP Property
			XmpProperty property = new XmpProperty(schema, propertyNode.LocalName, value);

			//回傳
			return property;
		}
		#endregion
		#region Reset
		/// <summary>
		/// 將列舉位置重新移動至第一個 XMP Property。
		/// </summary>
		/// <exception cref="ObjectDisposedException">列舉器已經處置。</exception>
		public void Reset()
		{
			//確認狀態
			if (_Disposed)
				throw new ObjectDisposedException("XmpPropertyEnumerator");

			//重設讀取位置
			if (_InitObject is Stream)
			{
				if (_InitStreamPosition == null)
					throw new InvalidOperationException();
				(_InitObject as Stream).Position = _InitStreamPosition.Value;
				_InitStreamPosition = null;
			}

			//重設欄位
			_CurrentProperty = null;
			_CurrentPropertyNode = null;
			_XmlDocument = null;
		}
		#endregion
	}
}
